package com.example.infrastructure.persistent.repository;

import com.example.domain.strategy.model.entity.StrategyEntity;
import com.example.domain.strategy.model.entity.StrategyRuleEntity;

import com.example.domain.strategy.model.valobj.*;
import com.example.infrastructure.persistent.dao.*;
import com.example.infrastructure.persistent.po.*;
import com.example.infrastructure.persistent.redis.IRedisService;
import com.example.domain.strategy.model.entity.StrategyAwardEntity;
import com.example.domain.strategy.repository.IStrategyRepository;
import com.example.types.common.Constants;
import com.example.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.example.types.enums.ResponseCode.UN_ASSEMBLED_STRATEGY_ARMORY;

/**
 * @Author: 三石
 * @desc: 策略仓储接口 实现类
 * @Date: 2024-07-18-22:24
 */
@Slf4j
@Repository
public class StrategyRepositoryImpl implements IStrategyRepository {

    @Resource
    private IStrategyAwardDao strategyAwardDao;

    @Resource
    private IStrategyDao strategyDao;

    @Resource
    private IStrategyRuleDao strategyRuleDao;

    @Resource
    private IRuleTreeDao ruleTreeDao;

    @Resource
    private IRuleTreeNodeDao ruleTreeNodeDao;

    @Resource
    private IRuleTreeNodeLineDao ruleTreeNodeLineDao;

    @Resource
    private IRedisService redisService;

    /**
     * 查询指定抽奖策略下的奖品集合
     * @param strategyId 策略ID
     */
    @Override
    public List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId) {
        // 拼接 redis key
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_LIST_KEY + strategyId;
        // 优先从缓存中获取
        log.info("从缓存中读取策略ID = {} 下的奖品集合, key: {}", strategyId, cacheKey);
        List<StrategyAwardEntity> strategyAwardEntities = redisService.getList(cacheKey, StrategyAwardEntity.class);
        if (null != strategyAwardEntities && !strategyAwardEntities.isEmpty()) {
            log.info("缓存中读取策略ID = {} 下的奖品集合, 缓存已命中!", strategyId);
            return strategyAwardEntities;
        }
        // 缓存未命中, 从数据库中读取数据
        log.info("缓存中读取策略ID = {} 下的奖品集合, 缓存未命中, 从数据库中读取...", strategyId);
        List<StrategyAwardPO> strategyAwardPOS = strategyAwardDao.queryStrategyAwardListByStrategyId(strategyId);
        // 数据转换
        strategyAwardEntities = new ArrayList<>(strategyAwardPOS.size());
        for(StrategyAwardPO strategyAwardPO : strategyAwardPOS) {
            StrategyAwardEntity strategyAwardEntity = StrategyAwardEntity.builder()
                    .strategyId(strategyAwardPO.getStrategyId())
                    .awardId(strategyAwardPO.getAwardId())
                    .awardTitle(strategyAwardPO.getAwardTitle())
                    .awardSubtitle(strategyAwardPO.getAwardSubtitle())
                    .awardCount(strategyAwardPO.getAwardCount())
                    .awardCountSurplus(strategyAwardPO.getAwardCountSurplus())
                    .awardRate(strategyAwardPO.getAwardRate())
                    .sort(strategyAwardPO.getSort())
                    .build();
            strategyAwardEntities.add(strategyAwardEntity);
        }
        // 缓存对象
        log.info("读取数据库中策略奖品合集完毕, 进行 redis 存储... key{}", cacheKey);
        redisService.setValue(cacheKey, strategyAwardEntities);
        // 返回结果
        return strategyAwardEntities;
    }

    /**
     * 存储抽奖策略相关信息到 redis
     * @param key redis key
     * @param rateRangeSize 抽奖策略范围值
     * @param shuffleStrategyAwardSearchRateTable 抽奖概率-奖品 查找表
     */
    @Override
    public void storeStrategyAwardSearchRateTable(String key, int rateRangeSize,
                                                  HashMap<Integer, Long> shuffleStrategyAwardSearchRateTable) {
        // 1. 存储抽奖策略范围值, 如 10000, 用于生成 10000 以内的随机数
        redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, rateRangeSize);
        // 2. 存储概率查找表
        Map<Integer, Long> cacheRateTable = redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key);
        cacheRateTable.putAll(shuffleStrategyAwardSearchRateTable);
    }

    /**
     * 获取 存储抽奖策略范围值
     * @param strategyId 策略ID
     */
    @Override
    public Integer getRateRange(Long strategyId) {
        return getRateRange(String.valueOf(strategyId));
    }

    @Override
    public Integer getRateRange(String key) {
        String cacheKey = Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key;
        if (!redisService.isExists(cacheKey)) {
            throw new AppException(UN_ASSEMBLED_STRATEGY_ARMORY.getCode(), cacheKey + Constants.COLON + UN_ASSEMBLED_STRATEGY_ARMORY.getInfo());
        }
        return redisService.getValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, Integer.class);
    }

    /**
     * 获取随机奖品 ID
     * @param strategyId 策略ID
     * @param randomRate 随机数, 也是查找表的key
     * @return 随机奖品 ID
     */
    @Override
    public Long getStrategyAwardAssemble(Long strategyId, int randomRate) {
        // 从查找表中获取
        return getStrategyAwardAssemble(String.valueOf(strategyId), randomRate);
    }

    @Override
    public Long getStrategyAwardAssemble(String key, int randomRate) {
        return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key, randomRate);
    }

    /**
     * 通过策略ID查询策略实体
     */
    @Override
    public StrategyEntity queryStrategyEntityByStrategyId(Long strategyId) {

        // 优先从缓存中获取
        String cacheKey = Constants.RedisKey.STRATEGY_KEY + strategyId;
        StrategyEntity strategyEntity = redisService.getValue(cacheKey, StrategyEntity.class);
        if (strategyEntity != null) {
            log.info("从redis中获取策略ID：{} 的策略实体, 缓存命中, 直接返回, key: {}", strategyId, cacheKey);
            return strategyEntity;
        }

        log.info("从redis中获取策略ID：{} 的策略实体, 缓存未命中, key: {}, 从数据库中读取", strategyId, cacheKey);
        StrategyPO strategyPO = strategyDao.queryStrategyPOByStrategyId(strategyId);
        if (strategyPO == null) {
            log.error("策略ID：{} 的策略实体在数据库中不存在!", strategyId);
            throw new AppException("invalid strategyId! Not found any entity by it. -> " + strategyId);
        }
        strategyEntity =  StrategyEntity.builder()
                .strategyId(strategyPO.getStrategyId())
                .strategyDesc(strategyPO.getStrategyDesc())
                .ruleModels(strategyPO.getRuleModels())
                .build();
        log.info("从数据库中读取策略ID: {} 的策略实体完成, 写入redis缓存 key: {}", strategyId, cacheKey);
        redisService.setValue(cacheKey, strategyEntity);
        return strategyEntity;
    }

    /**
     * 查询指定策略ID和抽奖规则类型的策略规则实体
     * @param strategyId 策略ID        big_market_strategy_key_100001
     * @param ruleModel 抽奖规则类型    big_market_strategy_key_100001
     * @return 策略规则实体
     */
    @Override
    public StrategyRuleEntity queryStrategyRuleEntity(Long strategyId, String ruleModel) {

        String cacheKey = Constants.RedisKey.STRATEGY_RULE_KEY + strategyId + ruleModel;
        StrategyRuleEntity strategyRuleEntity = redisService.getValue(cacheKey, StrategyRuleEntity.class);
        if (null != strategyRuleEntity) {
            log.info("从redis中获取策略ID: {} 抽奖规则类型: {} 的策略实体 -> 缓存命中,直接返回", strategyId, ruleModel);
            return strategyRuleEntity;
        }
        log.info("从redis中获取策略ID: {} 抽奖规则类型: {} 的策略实体 -> 缓存未命中,从数据库查询", strategyId, ruleModel);
        // 构造查询实体
        StrategyRulePO strategyRuleReq = new StrategyRulePO();
        strategyRuleReq.setStrategyId(strategyId);
        strategyRuleReq.setRuleModel(ruleModel);
        // 查询数据库
        StrategyRulePO strategyRulePO = strategyRuleDao.queryStrategyRulePO(strategyRuleReq);
        if (null == strategyRulePO) {
            log.error("策略ID: {} 抽奖规则类型: {} 的策略实体在数据库中不存在", strategyId, ruleModel);
            throw new AppException("invalid (strategyId: "  + strategyId + " and ruleModel: " + ruleModel + "). Not found any entity from database");
        }
        // 类型转换
        strategyRuleEntity =  StrategyRuleEntity.builder()
                .strategyId(strategyRulePO.getStrategyId())
                .awardId(strategyRulePO.getAwardId())
                .ruleType(strategyRulePO.getRuleType())
                .ruleModel(strategyRulePO.getRuleModel())
                .ruleValue(strategyRulePO.getRuleValue())
                .ruleDesc(strategyRulePO.getRuleDesc())
                .build();
        log.info("从数据库中读取策略ID: {} 抽奖规则类型: {} 的策略实体, 写入redis缓存", strategyId, ruleModel);
        redisService.setValue(cacheKey, strategyRuleEntity);
        return strategyRuleEntity;
    }

    /**
     * 根据 策略ID,奖品ID(可能是null),规则模型查询 规则模型信息
     * @param strategyId 策略ID
     * @param awardId 奖品ID
     * @param ruleModel 规则模型查询
     * @return 规则模型信息
     */
    @Override
    public String queryStrategyRuleValue(Long strategyId, Long awardId, String ruleModel) {
        // todo 存到redis
        StrategyRulePO strategyRule = new StrategyRulePO();
        strategyRule.setStrategyId(strategyId);
        strategyRule.setAwardId(awardId);
        strategyRule.setRuleModel(ruleModel);

        return strategyRuleDao.queryStrategyRuleValue(strategyRule);
    }

    /**
     * 根据 策略ID,奖品ID(可能是null),规则模型查询 规则模型信息
     * @param strategyId 策略ID
     * @param ruleModel 规则模型查询
     * @return 规则模型信息
     */
    @Override
    public String queryStrategyRuleValue(Long strategyId, String ruleModel) {
        return queryStrategyRuleValue(strategyId, null, ruleModel);
    }

    /**
     * 查询策略奖品配置的规则模型
     * @param strategyId 策略ID
     * @param awardId 奖品ID
     * @return 规则模型
     */
    @Override
    public StrategyAwardRuleModelVO queryStrategyAwardRuleModelVO(Long strategyId, Long awardId) {
        StrategyAwardPO strategyAwardReq =StrategyAwardPO.builder()
                .strategyId(strategyId)
                .awardId(awardId)
                .build();

        String ruleModel = strategyAwardDao.queryStrategyAwardRuleModel(strategyAwardReq);

        return StrategyAwardRuleModelVO.builder().ruleModels(ruleModel).build();
    }

    /**
     * 通过规则树ID查询（构建）规则树
     * @param treeId 树ID
     */
    @Override
    public RuleTreeVO queryRuleTreeVOByTreeId(String treeId) {

        // 优先从缓存获取
        String cacheKey = Constants.RedisKey.RULE_TREE_VO_KEY + treeId;
        RuleTreeVO ruleTreeVOCache = redisService.getValue(cacheKey, RuleTreeVO.class);
        if (null != ruleTreeVOCache)  {
            log.info("通过规则树ID查询（构建）规则树 缓存命中, key: {}", cacheKey);
            return ruleTreeVOCache;
        }

        // 查询根节点
        RuleTreePO ruleTreePO = ruleTreeDao.queryRuleTreeByTreeId(treeId);
        if (ruleTreePO == null) {
            return null;
        }

        // 查询所有的节点
        List<RuleTreeNodePO> ruleTreeNodePos = ruleTreeNodeDao.queryRuleTreeNodeListByTreeId(treeId);
        if (ruleTreeNodePos == null || ruleTreeNodePos.isEmpty()) {
            return null;
        }

        // 查询节点的连线关系
        List<RuleTreeNodeLinePO> ruleTreeNodeLinePos = ruleTreeNodeLineDao.queryRuleTreeNodeLineListByTreeId(treeId);
        if (ruleTreeNodeLinePos == null || ruleTreeNodeLinePos.isEmpty()) {
            return null;
        }

        // 1. tree node line 转换Map结构
        Map<String, List<RuleTreeNodeLineVO>> ruleTreeNodeLineMap = new HashMap<>();
        for (RuleTreeNodeLinePO ruleTreeNodeLine : ruleTreeNodeLinePos) {

            RuleTreeNodeLineVO ruleTreeNodeLineVO = RuleTreeNodeLineVO.builder()
                    .treeId(ruleTreeNodeLine.getTreeId())
                    .ruleNodeFrom(ruleTreeNodeLine.getRuleNodeFrom())
                    .ruleNodeTo(ruleTreeNodeLine.getRuleNodeTo())
                    .ruleLimitType(RuleLimitTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitType()))
                    .ruleLimitValue(RuleLogicCheckTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitValue()))
                    .build();

            // computeIfAbsent 是 Map 接口中的一个方法，用于根据指定的键查找值，如果找到了，则返回对应的值，
            // 如果没有找到，则使用提供的函数计算一个默认值，并将其放入 Map 中，然后返回该默认值。
            List<RuleTreeNodeLineVO> ruleTreeNodeLineVOList =
                    ruleTreeNodeLineMap.computeIfAbsent(ruleTreeNodeLine.getRuleNodeFrom(), k -> new ArrayList<>());
            ruleTreeNodeLineVOList.add(ruleTreeNodeLineVO);
        }
        log.info("map: {}", ruleTreeNodeLineMap);
        log.info("1. tree node line 【转换Map结构】完成");

        // 2. tree node 转换为Map结构
        HashMap<String, RuleTreeNodeVO> treeNodeMap = new HashMap<>();
        for (RuleTreeNodePO ruleTreeNode : ruleTreeNodePos) {
            RuleTreeNodeVO ruleTreeNodeVO = RuleTreeNodeVO.builder()
                    .treeId(ruleTreeNode.getTreeId())
                    .ruleKey(ruleTreeNode.getRuleKey())
                    .ruleDesc(ruleTreeNode.getRuleDesc())
                    .ruleValue(ruleTreeNode.getRuleValue())
                    .treeNodeLineVOList(ruleTreeNodeLineMap.get(ruleTreeNode.getRuleKey()))
                    .build();
            treeNodeMap.put(ruleTreeNode.getRuleKey(), ruleTreeNodeVO);
        }
        log.info("2. tree node 【转换Map结构】完成");

        // 3. 构建 Rule Tree
        RuleTreeVO ruleTreeVODB = RuleTreeVO.builder()
                .treeId(ruleTreePO.getTreeId())
                .treeName(ruleTreePO.getTreeName())
                .treeDesc(ruleTreePO.getTreeDesc())
                .treeRootRuleNode(ruleTreePO.getTreeRootRuleKey())
                .treeNodeMap(treeNodeMap)
                .build();
        log.info("3. 构建 Rule Tree 完成");

        redisService.setValue(cacheKey, ruleTreeVODB);
        log.info("Rule Tree 存储到redis成功！");
        RuleTreeVO value = redisService.getValue(cacheKey, RuleTreeVO.class);
        log.info(value.toString());
        return ruleTreeVODB;
    }

    /**
     * 缓存产品库存
     * @param key key
     * @param awardCount 库存量
     */
    @Override
    public void cacheStrategyAwardCount(String key, Integer awardCount) {
        if (redisService.isExists(key)) return;
        redisService.setAtomicLong(key, awardCount);
    }

    /**
     * 根据策略ID和奖品ID，扣减奖品缓存库存
     * @param key key
     * @return 扣减结果
     */
    @Override
    public Boolean subtractionAwardStock(String key) {
        long surplus = redisService.decr(key);
        if (surplus < 0) {
            // surplus 扣减后的库存小于0，恢复为0个
            redisService.setValue(key, 0);
            return false;
        }
        // 1. 按照cacheKey decr 后的值，如 99、98、97 和 key 组成为库存锁的key进行使用。
        // 2. 加锁为了兜底，如果后续有恢复库存，手动处理等，也不会超卖。因为所有的可用库存key，都被加锁了。
        String lockKey = key + Constants.UNDERLINE + surplus;
        Boolean lock = redisService.setNx(lockKey);
        if (!lock) {
            log.info("策略奖品库存加锁失败 {}", lockKey);
        }
        return lock;
    }

    /**
     * 写入延迟队列，延迟消费更新数据库记录
     */
    @Override
    public void awardStockConsumeSendQueue(StrategyAwardStockKeyVO strategyAwardStockKeyVO) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUEUE_KEY;
        // 通过 Redisson 客户端的 getBlockingQueue 方法获取一个阻塞队列对象 blockingQueue。
        // 这个阻塞队列用于存储 StrategyAwardStockKeyVO 类型的对象
        RBlockingQueue<StrategyAwardStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        // Redisson 客户端的 getDelayedQueue 方法获取一个延迟队列对象 delayedQueue
        RDelayedQueue<StrategyAwardStockKeyVO> delayedQueue = redisService.getDelayedQueue(blockingQueue);
        // strategyAwardStockKeyVO 对象将在当前时间的基础上延迟 3 秒后才会被取出
        delayedQueue.offer(strategyAwardStockKeyVO, 3, TimeUnit.SECONDS);

    }

    /**
     * 更新奖品库存消耗记录
     */
    @Override
    public StrategyAwardStockKeyVO takeQueueValue() {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUEUE_KEY;
        RBlockingQueue<StrategyAwardStockKeyVO> destinationQueue = redisService.getBlockingQueue(cacheKey);
        // 会从队列中取出并移除头部的元素，并将其返回。如果队列为空，poll 方法会立即返回 null
        return destinationQueue.poll();
    }

    /**
     * 更新奖品库存消耗记录
     *
     * @param strategyId 策略ID
     * @param awardId    奖品ID
     */
    @Override
    public void updateStrategyAwardStock(Long strategyId, Long awardId) {
        StrategyAwardPO strategyAwardReq = new StrategyAwardPO();
        strategyAwardReq.setStrategyId(strategyId);
        strategyAwardReq.setAwardId(awardId);
        strategyAwardDao.updateStrategyAwardStock(strategyAwardReq);
    }

    /**
     * 根据策略ID+奖品ID的唯一值组合，查询奖品信息
     *
     * @param strategyId 策略ID
     * @param awardId    奖品ID
     * @return 奖品信息
     */
    @Override
    public StrategyAwardEntity queryStrategyAwardEntity(Long strategyId, Long awardId) {
        // 优先从缓存获取
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_KEY + strategyId + Constants.UNDERLINE + awardId;
        StrategyAwardEntity strategyAwardEntity = redisService.getValue(cacheKey, StrategyAwardEntity.class);
        if (null != strategyAwardEntity) return strategyAwardEntity;
        // 查询数据
        StrategyAwardPO strategyAwardReq = new StrategyAwardPO();
        strategyAwardReq.setStrategyId(strategyId);
        strategyAwardReq.setAwardId(awardId);
        StrategyAwardPO strategyAwardRes = strategyAwardDao.queryStrategyAward(strategyAwardReq);
        if (strategyAwardRes == null) {
            log.error("根据策略ID: {}, 奖品ID: {} 的唯一值组合，查询奖品信息失败！-> {}", strategyId, awardId, null);
            throw new RuntimeException("根据策略ID+奖品ID的唯一值组合，查询奖品信息为null");
        }
        // 转换数据
        strategyAwardEntity = StrategyAwardEntity.builder()
                .strategyId(strategyAwardRes.getStrategyId())
                .awardId(strategyAwardRes.getAwardId())
                .awardTitle(strategyAwardRes.getAwardTitle())
                .awardSubtitle(strategyAwardRes.getAwardSubtitle())
                .awardCount(strategyAwardRes.getAwardCount())
                .awardCountSurplus(strategyAwardRes.getAwardCountSurplus())
                .awardRate(strategyAwardRes.getAwardRate())
                .sort(strategyAwardRes.getSort())
                .build();
        // 缓存结果
        redisService.setValue(cacheKey, strategyAwardEntity);
        // 返回数据
        return strategyAwardEntity;
    }

}
